﻿using Dora.Orm.Core.Domain.Entities;
using Dora.Orm.Core.Domain.UnitOfWork;
using Dora.Orm.SqlSugarCore.UnitOfWork;
using Dora.Tools.Utility.Redis;
using SqlSugar;
using Surging.Core.CPlatform.Ioc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;

namespace Dora.Orm.SqlSugarCore.Reponsitory
{
    public abstract class DoraSugarRepository<TEntity, TPrimaryKey> : BaseRepository, IDoraSugarRepository<TEntity, TPrimaryKey>
        where TEntity : Entity<TPrimaryKey>, new()
    {
        private readonly ISugarUnitOfWork _unitOfWork;
        private readonly string _redisKeyPrefix;
        private readonly string _redisSystemId;

        public DoraSugarRepository(ISugarUnitOfWork unitOfWork, string redisKeyprefix = null, string redisSystemId = null)
        {
            _unitOfWork = unitOfWork;
            _redisKeyPrefix = redisKeyprefix;
            _redisSystemId = redisSystemId;
        }

        public IUnitOfWork UnitOfWork { get => _unitOfWork; set => UnitOfWork = value; }

        #region FirstOrDefault
        
        [Obsolete("Asynchronous methods are recommended, except for calls in transactions.")]
        public TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
        {
            var data =  SugarGetAll().First(predicate);
            return data;
        }

        [Obsolete("Asynchronous methods are recommended, except for calls in transactions.")]
        public TEntity FirstOrDefault(TPrimaryKey id)
        {
            var data = _unitOfWork.QueryClient.Queryable<TEntity>().Where(x => x.Id.Equals(id)).First();
            return data;
        }

        public async Task<TEntity> FirstOrDefaultAsync(TPrimaryKey id)
        {
            var data =  await _unitOfWork.QueryClient.Queryable<TEntity>().Where(x => x.Id.Equals(id)).FirstAsync();
            return data;
        }

        public async Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
        {
            var data =  await _unitOfWork.QueryClient.Queryable<TEntity>().Where(predicate).FirstAsync();
            return data;
        }

        public async Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> predicate)
        {
            var data =  await SugarGetAll().SingleAsync(predicate);
            return data;
        }

        #endregion

        #region GetAllList/Select

        public virtual ISugarQueryable<TEntity> SugarGetAll()
        {
            var sugarQueryable =  _unitOfWork.QueryClient.Queryable<TEntity>();
            return sugarQueryable;
        }
        [Obsolete("Asynchronous methods are recommended, except for calls in transactions.")]
        public List<TEntity> GetAllList()
        {
            var allList =  SugarGetAll().ToList();
            return allList;
        }

        public async Task<List<TEntity>> GetAllListAsync()
        {
            var dataList = await SugarGetAll().ToListAsync();
            return dataList;
        }
        [Obsolete("Asynchronous methods are recommended, except for calls in transactions.")]
        public List<TEntity> GetAllList(Expression<Func<TEntity, bool>> predicate)
        {
            var dataList =  SugarGetAll().Where(predicate).ToList();
            return dataList;
        }

        public async Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate)
        {
            var dataList = await SugarGetAll().Where(predicate).ToListAsync();
            return dataList;
        }

        [Obsolete("This method is Obsolete, only supported by EntityFramemwork")]
        public IQueryable<TEntity> GetAll(bool isMaster = false)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region Insert
        [Obsolete("Asynchronous methods are recommended, except for calls in transactions.")]
        public TEntity Insert(TEntity entity)
        {
            var result =  _unitOfWork.ModifyClient.Insertable(entity).ExecuteReturnEntity();
            return result;
        }

        public async Task<TEntity> InsertAsync(TEntity entity)
        {
            var result = await  _unitOfWork.ModifyClient.Insertable(entity).ExecuteReturnEntityAsync();
            return result;
        }

        public async Task<TEntity> InsertAsync(Expression<Func<TEntity, bool>> predicate,TEntity entity)
        {
            var count = await SugarGetAll().CountAsync(predicate);
            if (count<=0)
            {
                var result = await _unitOfWork.ModifyClient.Insertable(entity).ExecuteReturnEntityAsync();
                return result;
            }
            return null;
        }

        #endregion

        #region Update
        [Obsolete("Asynchronous methods are recommended, except for calls in transactions.")]
        public bool Update(TEntity entity)
        {
            var result = _unitOfWork.ModifyClient.Updateable(entity).ExecuteCommandHasChange();
            return result;
        }

        public async Task<bool> UpdateAsync(TEntity entity)
        {
            var result = await _unitOfWork.ModifyClient.Updateable(entity).ExecuteCommandHasChangeAsync();
            return result;
        }
        [Obsolete("Asynchronous methods are recommended, except for calls in transactions.")]
        public bool UpdateColumn(TEntity entity, Expression<Func<TEntity, object>> funcColums)
        {
            var result = _unitOfWork.ModifyClient.Updateable(entity).UpdateColumns(funcColums).ExecuteCommandHasChange();
            return result;
        }

        public async Task<bool> UpdateColumnAsync(TEntity entity, Expression<Func<TEntity, object>> funcColums)
        {
            var result = await _unitOfWork.ModifyClient.Updateable(entity).UpdateColumns(funcColums).ExecuteCommandHasChangeAsync();
            return result;
        }

        #endregion

        #region Delete

        [Obsolete("Asynchronous methods are recommended, except for calls in transactions.")]
        public bool Delete(TPrimaryKey id)
        {
            var result = _unitOfWork.ModifyClient.Deleteable<TEntity>().In(id).ExecuteCommand() >= 0;
            return result;
        }

        [Obsolete("Asynchronous methods are recommended, except for calls in transactions.")]
        public bool Delete(TEntity entity)
        {
            var result = _unitOfWork.ModifyClient.Deleteable<TEntity>().Where(entity).ExecuteCommand() >= 0;
            return result;
        }
        [Obsolete("Asynchronous methods are recommended, except for calls in transactions.")]
        public bool Delete(Expression<Func<TEntity, bool>> predicate)
        {
            var result = _unitOfWork.ModifyClient.Deleteable<TEntity>(predicate).ExecuteCommand() >= 0;
            return result;
        }

        public async Task<bool> DeleteAsync(TPrimaryKey id)
        {
            var result = await _unitOfWork.ModifyClient.Deleteable<TEntity>().In(id).ExecuteCommandAsync() >= 0;
            return result;
        }

        public async Task<bool> DeleteAsync(TEntity entity)
        {
            var result = await _unitOfWork.ModifyClient.Deleteable<TEntity>().Where(entity).ExecuteCommandHasChangeAsync();
            return result;
        }

        public async Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> predicate)
        {
            var result = await _unitOfWork.ModifyClient.Deleteable<TEntity>().Where(predicate).ExecuteCommandHasChangeAsync();
            return result;
        }

        #endregion

        #region select/query

        public int Count()
        {
            var count = SugarGetAll().Count();
            return count;
        }

        public async Task<int> CountAsync()
        {
            var count = await SugarGetAll().CountAsync();
            return count;
        }

        public int Count(Expression<Func<TEntity, bool>> predicate)
        {
            var count = SugarGetAll().Count(predicate);
            return count;
        }

        public async Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate)
        {
            var count = await SugarGetAll().CountAsync(predicate);
            return count;
        }

        public async Task<ISugarQueryable<TEntity>> FindAllAsync(bool isMaster = false)
        {
            return await Task.Run(() =>
            {
                return _unitOfWork.QueryClient.Queryable<TEntity>();
            });
        }

        #endregion

        public async Task<TEntity> GetBothOrginAndCacheAsync(Expression<Func<TEntity, bool>> predicate, object id)
        {
            TEntity t;

            var key = GetCachingKey(id);
            t = await RedisManager.GetAsync<TEntity>(key);


            if (t == null)
            {
                t = await SugarGetAll().SingleAsync(predicate);
            }

            if (t == null)
                return null;
            else
            {
                await RedisManager.SetAsync(key, t);
                return t;
            }
        }

        public async Task<TEntity> GetBothOrginAsync(Expression<Func<TEntity, bool>> predicate, object id)
        {
            TEntity t;

            t = await RedisManager.GetAsync<TEntity>(GetCachingKey(id));

            if (t == null)
            {
                t = await SugarGetAll().SingleAsync(predicate);
            }

            return t;
        }

        public async Task<bool> DeleteBothOriginAsync(TPrimaryKey id)
        {
            var ok = await _unitOfWork.ModifyClient.Deleteable<TEntity>().In(id).ExecuteCommandHasChangeAsync();
            var result = await RedisManager.DelAsync(GetCachingKey(id));

            return true;
        }

        public async Task<TEntity> InsertAndCacheAsync<TCache>(TEntity entity) where TCache : class, new()
        {
            var result = await _unitOfWork.ModifyClient.Insertable(entity).ExecuteReturnEntityAsync();
            var cache = ConverToTCache<TCache>(entity);
            var ok = await RedisManager.SetAsync(GetCachingKey(result.Id), cache);

            return result;
        }

        public async Task<TEntity> UpdateAndCacheAsync<TCache>(TEntity entity) where TCache : class, new()
        {
            var result = await _unitOfWork.ModifyClient.Updateable(entity).ExecuteCommandAsync() > 0 ? entity : null;
            var cache = ConverToTCache<TCache>(entity);
            var ok = await RedisManager.SetAsync(GetCachingKey(result.Id), cache);

            return result;
        }
        
        private TCache ConverToTCache<TCache>(TEntity entity)
        {
            var cache = Activator.CreateInstance<TCache>();
            foreach (PropertyInfo info in typeof(TCache).GetProperties())
            {
                PropertyInfo pro = typeof(TEntity).GetProperty(info.Name);
                if (pro != null)
                    info.SetValue(cache, pro.GetValue(entity));
            }

            return cache;
        }
        private string GetCachingKey(object indentity)
        {
            var key = $"{_redisKeyPrefix}_{_redisSystemId}_{indentity}";
            return key;
        }
        
    }
}
